[Python] キャメルケースの辞書キーをスネークケースに変換する
こんにちは。サービスグループの武田です。
識別子の命名規則は普段どんなものを使っていますか?主なものとしては次の4種類が挙げられます。
- アッパーキャメルケース(パスカルケース)
- ローワーキャメルケース(単にキャメルケース)
- スネークケース
- ケバブケース(チェインケース)
使用するプログラミング言語などによって、採用率は異なるのが一般的です。たとえばJavaであればクラス名はアッパーキャメルケースで、メソッド名や変数名はローワーキャメルケース。Pythonであればクラス名はアッパーキャメルケースで変数名などはスネークケース、などが多い認識です(プロジェクトによってはこの限りではありません)。
それではJSONはどうでしょう。JSONはREST APIなどに代表されるデータ交換用のフォーマットのひとつで、軽量なテキストベースということもあり広く普及しています。JSONにはキーと値をペアとした、いわゆるマップとか連想配列や辞書などと呼ばれるデータ型が扱えます。キーは文字列なのですが、前述した命名規則がサービスによって割とバラバラだったりします。
軽く調べてみたところ、GitHubやFacebookなどはスネークケース。AWSやGoogle Cloudなどはキャメルケースでした。さてここで問題になったのが、Web APIのレスポンスデータ(JSON)をPythonで扱う際にキーの命名規則がずれてしまうことでした。Pythonで辞書型のデータを扱う際、普段はキーをスネークケースで扱っているのに、レスポンスデータはキャメルケースになってしまうのです。データのスコープがとても狭いのであればそこだけ注意すればいいのですが、たらい回しにするケースだと至るところで違いを意識する必要が出てしまい、ノイズになってしまいます。
そんなわけで簡単な変換用の関数を用意して解決してみました。
検証環境
おそらく古過ぎなければたいてい動くはずです。今回はこちら。
$ python3 -V Python 3.7.9
関数および動作確認
関数は次のようなものを定義しました。変換部分は別関数に切り出すことで、他の変換にも対応できるようにしています。
import re from typing import Any, Callable def camel_to_snake(s: str) -> str: return re.sub("((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))", r"_\1", s).lower() def convert_dict_key(d: dict, conv: Callable[[str], str]) -> dict: def convert_value(v: Any) -> Any: return ( convert_dict_key(v, conv) if isinstance(v, dict) else [convert_value(e) for e in v] if isinstance(v, list) else v ) return {conv(k): convert_value(v) for k, v in d.items()}
それでは実際に試してみます。
>>> convert_dict_key({"yourName":"mitsuha"}, camel_to_snake) {'your_name': 'mitsuha'} >>> >>> convert_dict_key({"HEROINE":{"yourName":"mitsuha"}}, camel_to_snake) {'heroine': {'your_name': 'mitsuha'}} >>> >>> convert_dict_key({"characters":[{"yourName":"taki"},{"yourName":"mitsuha"}]}, camel_to_snake) {'characters': [{'your_name': 'taki'}, {'your_name': 'mitsuha'}]}
大丈夫そうです!
まとめ
辞書のキーについては、「そういうものだ」と受け入れられれば済む問題ではありますが、コードの一貫性の観点から統一したいケースもあります。そういうときに思い出してもらえれば幸いです。